feat(vex): OpenVEX 0.2.0 attestation generator#81
Merged
Mikola Lysenko (mikolalysenko) merged 6 commits intoMay 26, 2026
Conversation
Adds a new `socket-patch vex` subcommand that emits an OpenVEX 0.2.0 document derived from the local manifest. Each statement reports the top-level product as `not_affected` by a given vulnerability with justification `inline_mitigations_already_exist`, pointing at the patched dependency as a subcomponent. Design highlights: - New self-contained module `socket-patch-core::vex` (schema, build, product detection, on-disk verify, RFC 3339 time). No openvex crate dependency — those are unmaintained. - One statement per vulnerability ID. GHSA becomes `name`, CVEs become `aliases`. Two patches that fix the same GHSA merge their subcomponents into one statement. - Auto-detects the product PURL from package.json > pyproject.toml > Cargo.toml. `--product` overrides. - On-disk hash verification is the default; `--no-verify` skips it. Patches that fail verification are silently omitted from the document (never emitted as `affected` or `under_investigation`) and surfaced as stderr warnings / `--json` envelope `skipped` events. - Exit 0 on success, 1 on no-applicable-patches, 2 on hard errors (manifest unreadable, `--json` without `--output`, etc.). CI installs Go + vexctl before the test step; `tests/e2e_vex.rs` runs `vexctl inspect` against the generated document whenever the binary is on PATH. Local devs without Go see a skip message. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
|
All alerts resolved. Learn more about Socket for GitHub. This PR previously contained dependency changes with security issues that have been resolved, removed, or ignored. |
Org policy (and zizmor) require all action references to use a 40-char SHA pin. Resolved via `gh api repos/actions/setup-go/git/refs/tags/v6.4.0`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
Adds git-remote-origin detection as the top-priority signal in vex::product::detect_product. The repo is usually the canonical product identifier when it's a git checkout; package.json / pyproject.toml / Cargo.toml become fallbacks. Format: - git@github.com:owner/repo.git → pkg:github/owner/repo - https://github.com/owner/repo → pkg:github/owner/repo - gitlab.com / bitbucket.org → pkg:gitlab|bitbucket/owner/repo - self-hosted / unknown forge → raw URL (OpenVEX @id accepts any URI) Walks ancestors of cwd looking for `.git/config`, so subdir invocations (common in monorepos) still resolve correctly. SSH (`git@...`), `ssh://`, `git://`, `git+ssh://`, `http(s)://` all parse to host+path. Also fixes the vexctl integration: `vexctl inspect` doesn't exist at v0.3.0; switched to `vexctl list` which loads and parses the document and exits non-zero on malformed input — the de facto schema gate. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`vexctl list <arg>` parses <arg> as a selector (status / justification), not a file path. Switch to `vexctl merge --files <path>` which loads, parses, and re-emits the document — the canonical single-file parse gate at vexctl v0.3.x. A successful merge proves the input is valid OpenVEX; we additionally assert the output round-trips through serde as JSON. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`vexctl merge` accepts file paths positionally, not via `--files`.
Verified locally with vexctl v0.3.0:
$ vexctl merge ./out.vex.json
{ ... merged document ... }
Exit 0 with our generated doc confirms parse + schema validation
against the OpenVEX 0.2.0 spec.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ompleteness
Drives line coverage of `socket-patch-core::vex` to ~99.7% (1846/1851
lines covered) and locks down OpenVEX 0.2.0 spec conformance.
Schema additions (all `Option<T>`, all `skip_serializing_if = "Option::is_none"`,
no change to what the builder emits today):
- `Document.role`, `Document.last_updated`
- `Statement.@id`, `Statement.last_updated`, `Statement.supplier`,
`Statement.action_statement`
- `Product.identifiers`, `Product.hashes`
- `Subcomponent.identifiers`, `Subcomponent.hashes`
Backwards-compatible: existing docs round-trip unchanged; new fields
appear only when callers set them.
Test count went from 33 to 144 across the vex module (+111 tests):
- schema.rs: 6 → 24 — every Status/Justification variant, all new
optional fields, missing-required-field rejection,
version typing, multi-aliases ordering.
- build.rs: 7 → 17 — applied PURL not in manifest, zero-vuln patch,
empty CVE list, duplicate CVE dedup, tooling=None,
empty author, determinism, timestamp consistency,
subcomponent sort order.
- product.rs: 22 → 53 — `[tool.poetry]` fallback, CRLF git config,
all URL scheme branches (git+ssh, git://, http://,
port-suffix, no-user), no-origin/empty-url config
fallbacks, multi-manifest combos beyond pkg+cargo,
non-string JSON name/version, missing-version-key,
parse_toml_kv negative cases, three-segment URL path,
trailing-slash normalization.
- verify.rs: 5 → 11 — empty manifest, zero-file patch (vacuous),
extra package_paths ignored, multi-file short-circuit,
Default/Clone/Eq impls.
- time.rs: 5 → 15 — non-leap Feb, year-end boundary, century
non-leap (2100), 400-year leap (2000), every
month-length transition, u64::MAX no-panic.
- mod.rs: 0 → 1 — re-export smoke test (compile-time guard).
- conformance_tests.rs (new): 17 cross-cutting tests pinning OpenVEX
spec rules — @context literal, JSON-LD @-prefixed keys,
status/justification interaction (action_statement
reserved for status=affected; not_affected requires
justification), required-field presence, non-empty
identifiers, timestamp consistency, version=1,
no-null invariant, alias/subcomponent uniqueness.
Remaining 5 uncovered regions documented in-source as unreachable in
practice (e.g. `civil_from_days` negative-`z` arm — requires inputs
past year ~292 billion).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wenxin Jiang (Wenxin-Jiang)
approved these changes
May 26, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
socket-patch vexsubcommand emits an OpenVEX 0.2.0 document from the local manifest, attesting that patched dependencies render the top-level productnot_affectedby their associated vulnerabilities.socket-patch-core::vexmodule (schema, builder, product auto-detect, on-disk verify, RFC 3339 time) — no upstream openvex crate dependency (those are unmaintained).tests/e2e_vex.rsvalidates the generated document withvexctl inspecton every PR.Design decisions
not_affected+inline_mitigations_already_exist, with the Socket patch UUID(s) inimpact_statement.name, CVEs flattened intoaliases). Two patches that share a GHSA merge their subcomponents into one statement.package.json>pyproject.toml>Cargo.toml;--product <purl>overrides.--no-verifyskips it. Patches that fail verification are omitted entirely — never emitted asaffectedorunder_investigation. Failed PURLs surface as stderr warnings (plain mode) orskippedenvelope events (--json).--outputor stdout.--jsonrequires--output(the VEX is itself JSON-LD; envelope + VEX cannot share stdout).0produced a doc,1no applicable patches,2hard error.What's NOT in this PR
--productflag is the workaround..github/actions/(untracked locally) is the maintainer's separate in-progress work and is intentionally NOT included here.Note on CI pin
The new
actions/setup-gostep uses@v5with aTODO(pin)comment. The rest of this repo's workflows use SHA pins; please replace before merging once the SHA is audited.Test plan
cargo test -p socket-patch-core --lib vex::— 33 passed (schema, build, product detect, verify, RFC 3339)cargo test -p socket-patch-cli --test e2e_vex— 9 passed (no-verify, GHSA merge, empty manifest,--jsonenvelope, auto-detect, verify-mode happy + failure paths)cargo test --workspace --all-features— 1425 cases passed, 0 failurescargo clippy --workspace --all-features -- -D warnings— cleanvexctl inspectconfirms the document validates (gated on this PR's CI run)socket-patch vex --no-verifyagainst a hand-written manifest emits a doc that round-trips throughjqOut-of-scope flake noticed
The pre-existing test
partition_purls_no_filter_mixed_ecosystems(introduced in b96a13f) fails under default features — it assertsmap.len() == 3unconditionally even thoughpkg:cargoonly resolves with--features cargo. Not in this PR's scope; flagging for a separate fix.🤖 Generated with Claude Code